package cn.demoncat.util.pay.unionpay.util;

import cn.demoncat.util.exception.BizRuntimeException;
import cn.demoncat.util.io.ZipUtil;
import cn.demoncat.util.lang.LocalDateUtil;
import cn.demoncat.util.lang.LongUtil;
import cn.demoncat.util.lang.MapUtil;
import cn.demoncat.util.lang.StringUtil;
import cn.demoncat.util.lang.constant.CharsetConstant;
import cn.demoncat.util.pay.unionpay.config.UnionpayProperties;
import cn.demoncat.util.pay.unionpay.constant.UnionpayConstant;
import cn.demoncat.util.pay.unionpay.constant.UnionpayConstant.TxnType;
import cn.demoncat.util.pay.unionpay.entity.*;
import cn.demoncat.util.pay.unionpay.entity.UnionpayResultFile.Bill;
import cn.demoncat.util.sec.Base64Util;
import cn.demoncat.util.web.ServletUtil;
import com.unionpay.acp.sdk.AcpService;
import com.unionpay.acp.sdk.SDKConfig;
import com.unionpay.acp.sdk.SDKConstants;
import com.unionpay.acp.sdk.SDKUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.time.LocalDate;
import java.util.*;
import java.util.Map.Entry;

/**
 * 银联支付 - 在线网关支付
 * 
 * @author 延晓磊
 *
 * @since 2020年3月24日
 */
@Component
public class UnionpayOnlineUtil {

	@Resource
	private UnionpayUtil unionpayUtil;
	@Resource
	private UnionpayProperties unionpayProperties;
	
	/**
	 * 业务类型：000201 在线网关支付
	 */
	private static final String BIZ_TYPE = "000201";
	/**
	 * 渠道类型：07 PC，08 PH
	 */
	private static final String CHANNEL_TYPE = "07";
	
	/**
	 * 对账文件占位索引：交易代码 ~ 商户代码
	 */
	private static final int[] FILE_ARR_INDEX = {3,11,11,6,10,19,12,4,2,21,2,32,2,6,10,13,13,4,15,2,2,6,2,4,32,1,21,15};
	
	private static final Logger LOG = LoggerFactory.getLogger(UnionpayOnlineUtil.class);
	
	/**
	 * 支付
	 * <br/><br/>
	 * 整合请求参数，输出HTML表单，然后自动跳转到银联
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年3月24日
	 */
	public void pay(UnionpayParamPay param) {
		Map<String,String> map = new HashMap<>();
		// 固定参数
		map.put(SDKConstants.param_version, SDKConfig.version);
		map.put(SDKConstants.param_encoding, CharsetConstant.UTF8);
		map.put(SDKConstants.param_bizType, BIZ_TYPE);
		map.put(SDKConstants.param_currencyCode, UnionpayConstant.CURRENCY_CODE);
		map.put(SDKConstants.param_signMethod, SDKConfig.signMethod);
		map.put(SDKConstants.param_accessType, UnionpayConstant.ACCESS_TYPE);
		map.put(SDKConstants.param_channelType, CHANNEL_TYPE);
		
		// 配置参数
		map.put(SDKConstants.param_merId, unionpayProperties.getMerId());
		map.put(SDKConstants.param_backUrl, SDKConfig.backUrl);
		map.put(SDKConstants.param_frontUrl, SDKConfig.frontUrl);
		map.put(SDKConstants.param_frontFailUrl, SDKConfig.frontFailUrl);
		
		// 交易类型
		map.put(SDKConstants.param_txnType, TxnType.PAY);
		map.put(SDKConstants.param_txnSubType, "01");
		
		// 支付金额
		map.put(SDKConstants.param_txnAmt, param.getAmount().toString());
		// 支付时间
		map.put(SDKConstants.param_txnTime, LocalDateUtil.toNum(param.getTxnTime()));
		// 支付超时
		map.put(SDKConstants.param_payTimeoutTime, LocalDateUtil.toNum(param.getEndTime()));
		// 支付单号
		map.put(SDKConstants.param_orderId, param.getOrderId());
		// 业务保留域
		map.put(SDKConstants.param_reqReserved, param.getReqReserved());
		
		// 参数签名，创建并跳转表单页面
		createAndSendFormHtml(AcpService.sign(map));
	}
	
	/**
	 * 撤销支付
	 * <br/><br/>
	 * 对当日未结算交易进行全额退款，如果失败就走退款接口
	 *  
	 * @author 延晓磊
	 *
	 * @since 2020年3月24日
	 */
	public UnionpayResult cancel(UnionpayParamRefund param) {
		Map<String,String> map = new HashMap<>();
		// 固定参数
		map.put(SDKConstants.param_version, SDKConfig.version);
		map.put(SDKConstants.param_encoding, CharsetConstant.UTF8);
		map.put(SDKConstants.param_bizType, BIZ_TYPE);
		map.put(SDKConstants.param_currencyCode, UnionpayConstant.CURRENCY_CODE);
		map.put(SDKConstants.param_signMethod, SDKConfig.signMethod);
		map.put(SDKConstants.param_accessType, UnionpayConstant.ACCESS_TYPE);
		map.put(SDKConstants.param_channelType, CHANNEL_TYPE);
		
		// 配置参数
		map.put(SDKConstants.param_merId, unionpayProperties.getMerId());
		map.put(SDKConstants.param_backUrl, SDKConfig.backUrl);
		
		// 交易类型
		map.put(SDKConstants.param_txnType, TxnType.CANCEL);
		map.put(SDKConstants.param_txnSubType, "00");
		
		// 撤销金额：支付金额
		map.put(SDKConstants.param_txnAmt, param.getAmount().toString());
		// 撤销时间
		map.put(SDKConstants.param_txnTime, LocalDateUtil.toNum(param.getTxnTime()));
		// 撤销单号
		map.put(SDKConstants.param_orderId, param.getOrderId());
		// 原支付查询流水号
		map.put(SDKConstants.param_origQryId, param.getQueryId());
		// 业务保留域
		map.put(SDKConstants.param_reqReserved, param.getReqReserved());
		
		// 参数签名，POST请求，响应验签
		map = AcpService.post(AcpService.sign(map), SDKConfig.backRequestUrl);
		
		// 整合响应：返回00表示受理，需要通过异步响应来进一步确认
		return new UnionpayResult(TxnType.CANCEL, map.get(SDKConstants.param_respCode), map.get(SDKConstants.param_respMsg), map.get(SDKConstants.param_reqReserved));
	}
	
	/**
	 * 退款
	 * <br/><br/>
	 * 支持部分退款、多次退款，累计不超过支付金额。
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年3月24日
	 */
	public UnionpayResult refund(UnionpayParamRefund param) {
		Map<String,String> map = new HashMap<>();
		// 固定参数
		map.put(SDKConstants.param_version, SDKConfig.version);
		map.put(SDKConstants.param_encoding, CharsetConstant.UTF8);
		map.put(SDKConstants.param_bizType, BIZ_TYPE);
		map.put(SDKConstants.param_currencyCode, UnionpayConstant.CURRENCY_CODE);
		map.put(SDKConstants.param_signMethod, SDKConfig.signMethod);
		map.put(SDKConstants.param_accessType, UnionpayConstant.ACCESS_TYPE);
		map.put(SDKConstants.param_channelType, CHANNEL_TYPE);
		
		// 配置参数
		map.put(SDKConstants.param_merId, unionpayProperties.getMerId());
		map.put(SDKConstants.param_backUrl, SDKConfig.backUrl);
		
		// 交易类型
		map.put(SDKConstants.param_txnType, TxnType.REFUND);
		map.put(SDKConstants.param_txnSubType, "00");
		
		// 退款金额：累计不大于支付金额
		map.put(SDKConstants.param_txnAmt, param.getAmount().toString());
		// 退款时间
		map.put(SDKConstants.param_txnTime, LocalDateUtil.toNum(param.getTxnTime()));
		// 退款单号
		map.put(SDKConstants.param_orderId, param.getOrderId());
		// 原支付查询流水号
		map.put(SDKConstants.param_origQryId, param.getQueryId());
		// 业务保留域
		map.put(SDKConstants.param_reqReserved, param.getReqReserved());
		
		// 参数签名，POST请求，响应验签
		map = AcpService.post(AcpService.sign(map), SDKConfig.backRequestUrl);
		
		// 整合响应：返回00表示受理，需要通过异步响应来进一步确认
		return new UnionpayResult(TxnType.REFUND, map.get(SDKConstants.param_respCode), map.get(SDKConstants.param_respMsg), map.get(SDKConstants.param_reqReserved));
	}
	
	/**
	 * 异步通知
	 * <br/><br/>
	 * 支付前、后端通知，撤销后端通知，退款后端通知
	 * 
	 * @param request
	 * 
	 * @return 响应消息
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年3月27日
	 */
	public UnionpayResultNotify notify(HttpServletRequest request) {
		// 获取全部请求参数
		Map<String, String> map = MapUtil.clearEmpty(ServletUtil.getParams(request));
		// 商户号校验
		String merId = map.get(SDKConstants.param_merId);
		if (!unionpayProperties.getMerId().equals(merId)) {
			LOG.error("银联支付通知-商户号不匹配：配置商户 = {}，通知商户 = {}", unionpayProperties.getMerId(), merId);
			throw new BizRuntimeException("数据验签失败");
		}
		// 签名校验
		AcpService.validate(map);

		// 交易信息
		UnionpayResultNotify result = new UnionpayResultNotify(map.get(SDKConstants.param_txnType),map.get(SDKConstants.param_respCode),map.get(SDKConstants.param_respMsg), map.get(SDKConstants.param_reqReserved));
		result.setQueryId(map.get(SDKConstants.param_queryId));
		result.setOrigQryId(map.get(SDKConstants.param_origQryId));
		result.setOrderId(map.get(SDKConstants.param_orderId));
		result.setAccNo(map.get(SDKConstants.param_accNo));
		result.setTxnAmt(LongUtil.valueOf(map.get(SDKConstants.param_txnAmt)));
		result.setTxnTime(LocalDateUtil.toLocalDateTimeNum(map.get(SDKConstants.param_txnTime)));
		return result;
	}
	
	/**
	 * 交易查询
	 * <br/><br/>
	 * 支付、撤销、退款状态查询
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年3月24日
	 */
	public UnionpayResultQuery query(UnionpayParamQuery param) {
		Map<String,String> map = new HashMap<>();
		// 固定参数
		map.put(SDKConstants.param_version, SDKConfig.version);
		map.put(SDKConstants.param_encoding, CharsetConstant.UTF8);
		map.put(SDKConstants.param_bizType, BIZ_TYPE);
		map.put(SDKConstants.param_signMethod, SDKConfig.signMethod);
		map.put(SDKConstants.param_accessType, UnionpayConstant.ACCESS_TYPE);
		
		// 配置参数
		map.put(SDKConstants.param_merId, unionpayProperties.getMerId());
		
		// 交易类型
		map.put(SDKConstants.param_txnType, TxnType.QUERY);
		map.put(SDKConstants.param_txnSubType, "00");
		
		// 交易时间
		map.put(SDKConstants.param_txnTime, LocalDateUtil.toNum(param.getTxnTime()));
		// 交易单号
		map.put(SDKConstants.param_orderId, param.getOrderId());
		
		// 参数签名，POST请求，响应验签
		map = AcpService.post(AcpService.sign(map), SDKConfig.singleQueryUrl);
		
		// 整合响应：respCode返回00表示查询成功，origRespCode返回00表示交易成功
		UnionpayResultQuery result = new UnionpayResultQuery(map.get(SDKConstants.param_txnType), map.get(SDKConstants.param_respCode), map.get(SDKConstants.param_respMsg), map.get(SDKConstants.param_reqReserved));
		if (result.isSuccess()) {
			result.setAccNo(map.get(SDKConstants.param_accNo));
			result.setOrderId(map.get(SDKConstants.param_orderId));
			result.setOrigRespCode(map.get(SDKConstants.param_origRespCode));
			result.setOrigRespMsg(map.get(SDKConstants.param_origRespMsg));
			result.setQueryId(map.get(SDKConstants.param_queryId));
			result.setTxnAmt(LongUtil.valueOf(map.get(SDKConstants.param_txnAmt)));
			result.setTxnTime(LocalDateUtil.toLocalDateTimeNum(map.get(SDKConstants.param_txnTime)));
		}
		return result;
	}
	
	/**
	 * 对账文件
	 * <br/><br/>
	 * 获取15天内的对账文件
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年3月24日
	 */
	public UnionpayResultFile file(UnionpayParamFile param) {
		// 账单日期
		LocalDate now = LocalDate.now();
		if (param.getSettleDate().isAfter(now) || param.getSettleDate().isBefore(now.minusDays(15))) {
			throw new BizRuntimeException("只能查询15天内的对账文件");
		}
		int year = now.getYear();
		if (param.getSettleDate().getMonthValue() > now.getMonthValue()) {
			// 跨年结算
			year = year - 1;
		}
		
		// 固定参数
		Map<String,String> map = new HashMap<>();
		map.put(SDKConstants.param_version, SDKConfig.version);
		map.put(SDKConstants.param_encoding, CharsetConstant.UTF8);
		map.put(SDKConstants.param_bizType, "000000");
		map.put(SDKConstants.param_signMethod, SDKConfig.signMethod);
		map.put(SDKConstants.param_accessType, UnionpayConstant.ACCESS_TYPE);
		
		// 配置参数
		map.put(SDKConstants.param_merId, unionpayProperties.getMerId());
		
		// 交易类型
		map.put(SDKConstants.param_txnType, TxnType.FILE);
		map.put(SDKConstants.param_txnSubType, "01");
		
		
		// 对账时间
		map.put(SDKConstants.param_txnTime, LocalDateUtil.toNum(param.getTxnTime()));
		// 清算日期
		map.put(SDKConstants.param_settleDate, param.getSettleDate().format(UnionpayConstant.SETTLE_DATE_FORMAT));
		// 文件类型
		map.put(SDKConstants.param_fileType, "00"); 
		
		// 参数签名，POST请求，响应验签
		map = AcpService.post(AcpService.sign(map), SDKConfig.fileTransUrl);
		
		// 整合响应
		UnionpayResultFile result = new UnionpayResultFile(TxnType.FILE, map.get(SDKConstants.param_respCode), map.get(SDKConstants.param_respMsg), map.get(SDKConstants.param_reqReserved));
		if (result.isSuccess()) {
			// 账单文件
			result.setFileContent(SDKUtil.inflater(Base64Util.decode(map.get(SDKConstants.param_fileContent))));
			result.setFileName(map.get(SDKConstants.param_fileName));
			// 账目文件
			Map<String, String> files = ZipUtil.readToStr(new ByteArrayInputStream(result.getFileContent()), null, ".*ZM_.*");
			if (MapUtil.isNotEmpty(files)) {
				String zm = new ArrayList<>(files.values()).get(0).trim();
				if (StringUtils.isNotBlank(zm)) {
					// 切割行
					List<Bill> bills = new ArrayList<>();
					Bill bill;
					Map<Integer,String> cols;
					int leftIndex;
					int rightIndex;
					for (String line : StringUtil.splitLine(zm)) {
						line = line.trim();
						if (StringUtils.isNotBlank(line)) {
							// 根据占位切割列
							cols = new LinkedHashMap<>();
							leftIndex = 0;
							for(int i=0; i<FILE_ARR_INDEX.length; i++){
								rightIndex = leftIndex + FILE_ARR_INDEX[i];
								cols.put(i, StringUtils.trimToEmpty(line.substring(leftIndex,rightIndex)));
								leftIndex = rightIndex + 1;
							}
							// 整合账单
							bill = new Bill();
							bill.setMerId(cols.get(27));
							bill.setOrderId(cols.get(11));
							bill.setQueryId(cols.get(9));
							bill.setOrigQryId(cols.get(26));
							bill.setAccNo(cols.get(5));
							bill.setTxnAmt(LongUtil.valueOfZero(cols.get(6)));
							bill.setTxnCharge(LongUtil.valueOfZero(cols.get(15)));
							// - 日期：MMDDhhmmss
							bill.setTxnTime(LocalDateUtil.toLocalDateTimeNum(year + cols.get(4)));
							bill.setTxnType(cols.get(19));
							bills.add(bill);
						}
					}
				}
			}
		}
		return result;
	}
	
	
	
	/**
	 * 创建并跳转银联表单页面
	 * 
	 * @param param		表单参数
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年3月26日
	 */
	private static void createAndSendFormHtml(Map<String, String> param) {
		// 创建表单页面
		StringBuilder sf = new StringBuilder();
		sf.append("<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset="+CharsetConstant.UTF8+"\"/></head><body>");
		sf.append("<form id = \"pay_form\" action=\"").append(SDKConfig.frontRequestUrl).append("\" method=\"post\">");
		for (Entry<String, String> entry : param.entrySet()) {
			sf.append("<input type=\"hidden\" name=\"").append(entry.getKey()).append("\" id=\"").append(entry.getKey()).append("\" value=\"").append(entry.getValue()).append("\"/>");
		}
		sf.append("</form>");
		sf.append("</body>");
		sf.append("<script type=\"text/javascript\">");
		sf.append("document.all.pay_form.submit();");
		sf.append("</script>");
		sf.append("</html>");
		// 前端跳转页面
		ServletUtil.responseText(sf.toString());
	}
}
